package my.opencv;

import org.opencv.core.*;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import org.opencv.objdetect.CascadeClassifier;

import java.util.ArrayList;
import java.util.List;

import static org.opencv.imgcodecs.Imgcodecs.imwrite;

public class OpencvImageUtils {

    public static Mat drawRects(Rect[] rects,Mat mat) {
        if(null == rects|| rects.length ==0){
            System.out.println("没有方框");
            return null;
        }
        Mat draw = mat.clone();
        if(rects != null && rects.length >0){
            for(Rect rect:rects){
                Imgproc.rectangle(draw,new Rect(rect.x,rect.y,rect.width,rect.height),new Scalar(0, 255, 255),2,2);
            }
            Imgcodecs.imwrite("C:/Users/admin/Desktop/cut/test_eye_draw.jpg", draw);
        }
        return draw;

    }

    /**
     * 模糊处理
     * @param mat
     * @return
     */
    public static Mat blur (Mat mat) {
        Mat blur = new Mat();
        Imgproc.blur(mat,blur,new Size(5,5));
        return blur;
    }

    /**腐蚀和膨胀是对白色部分（高亮部分）而言的，不是黑色部分。膨胀就是图像中的高亮部分进行膨胀，“领域扩张”，效果图拥有比原图更大的高亮区域。腐蚀就是原图中的高亮部分被腐蚀，“领域被蚕食”，效果图拥有比原图更小的高亮区域。
     *膨胀
     * @param mat
     * @return
     */
    public static Mat dilate (Mat mat,int size){
        Mat dilate=new Mat();
        Mat element = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(size,size));
        //膨胀
        Imgproc.dilate(mat, dilate, element, new Point(-1, -1), 1);
        return dilate;
    }

    /**腐蚀和膨胀是对白色部分（高亮部分）而言的，不是黑色部分。膨胀就是图像中的高亮部分进行膨胀，“领域扩张”，效果图拥有比原图更大的高亮区域。腐蚀就是原图中的高亮部分被腐蚀，“领域被蚕食”，效果图拥有比原图更小的高亮区域。
     * 腐蚀
     * @param mat
     * @return
     */
    public static Mat erode (Mat mat,int size){
        Mat erode=new Mat();
        Mat element = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(size,size));
        //腐蚀
        Imgproc.erode(mat, erode, element, new Point(-1, -1), 1);
        return erode;
    }

    /**
     * thresh：阈值

     *threshold:是设定的阈值
     * maxval是当灰度值大于（或小于）阈值时将该灰度值赋成的值
     * type规定的是当前二值化的方式
     *
     * maxval：dst图像中最大值
     * type:
     * THRESH_BINARY
     * THRESH_BINARY_INV
     * THRESH_TRUNC
     * THRESH_TOZERO
     * THRESH_TOZERO_INV
     * THRESH_MASK	不支持
     * THRESH_OTSU	不支持32位
     * THRESH_TRIANGLE	不支持32位
     * @param mat
     * @return
     */
    public static Mat threshold (Mat mat){
        Mat threshold=new Mat();
        Double d=Imgproc.threshold(mat,threshold,140,200, Imgproc.THRESH_BINARY);
        return threshold;
    }

    public static Mat drawRect(Rect rect,Mat mat) {
        if(null == rect){
            System.out.println("没有方框");
            return null;
        }
        Mat draw = mat.clone();
        Imgproc.rectangle(draw,new Rect(rect.x,rect.y,rect.width,rect.height),new Scalar(0, 255, 255),2,2);
        Imgcodecs.imwrite("C:/Users/admin/Desktop/cut/test_eye_draw.jpg", draw);
        return draw;

    }

    public static Rect getMixRect(Rect[] rects) {
        Rect mix = null;
        if(rects != null && rects.length >0) {
            // 在每一个识别出来的人脸周围画出一个方框
            for (Rect r : rects) {
                if (mix == null) {
                    mix = r;
                } else {
                    if (r.width < mix.width) {
                        mix = r;
                    }
                }
            }
        }
        return mix;
    }

    public static Mat cutRect(Rect rect,Mat mat) {
        return new Mat(mat,rect);
    }

    /**
     * 图片旋转
     * @param splitImage
     * @param angle
     * @return
     */
    public static Mat rotate3(Mat splitImage, double angle){
        double thera = angle * Math.PI / 180;
        double a = Math.sin(thera);
        double b = Math.cos(thera);

        int wsrc = splitImage.width();
        int hsrc = splitImage.height();

        int wdst = (int) (hsrc * Math.abs(a) + wsrc * Math.abs(b));
        int hdst = (int) (wsrc * Math.abs(a) + hsrc * Math.abs(b));
        Mat imgDst = new Mat(hdst, wdst, splitImage.type());

        Point pt = new Point(splitImage.cols() / 2, splitImage.rows() / 2);
        // 获取仿射变换矩阵
        Mat affineTrans = Imgproc.getRotationMatrix2D(pt, angle, 1.0);

        //System.out.println(affineTrans.dump());
        // 改变变换矩阵第三列的值
        affineTrans.put(0, 2, affineTrans.get(0, 2)[0] + (wdst - wsrc) / 2);
        affineTrans.put(1, 2, affineTrans.get(1, 2)[0] + (hdst - hsrc) / 2);


        /**
         *   第1个参数：OutputArray dst：输出的图像
         *   第2个参数：InputArray M：透视变换的矩阵
         *   第3个参数：Size dsize：输出图像的大小
         *   第4个参数：int flags=INTER_LINEAR：输出图像的插值方法，可以为
         *             INTER_LINEAR 线性插值；
         *             INTER_NEAREST 最近邻插值；
         *             INTER_AREA 区域插值
         *             INTER_CUBIC 三次条样插值
         *             CV_WARP_INVERSE_MAP：指定 matrix 是输出图像到输入图像的反变换，因此可以直接用来做象素插值。否则, 函数从 map_matrix 得到反变换。
         *             CV_WARP_FILL_OUTLIERS：填充所有缩小图像的象素。如果部分象素落在输入图像的边界外，那么它们的值设定为 fillval（fillval
         *                  用来填充边界外面的值）.
         *    第5个参数：int borderMode：图像边界的处理方式，默认是BORDER_CONSTANT（即指定常数值填充） ，实质上，边界处理类型，该枚举型还有：
         *
         * Enumerator
         * BORDER_CONSTANT
         * iiiiii|abcdefgh|iiiiiii with some specified i（指定常数填充）
         * BORDER_REPLICATE
         * aaaaaa|abcdefgh|hhhhhhh（复制边缘像素填充）
         * BORDER_REFLECT
         * fedcba|abcdefgh|hgfedcb（反射复制边界像素）
         * BORDER_WRAP
         * cdefgh|abcdefgh|abcdefg
         * BORDER_REFLECT_101
         * gfedcb|abcdefgh|gfedcba（对称填充，也就是以最边缘像素为轴）
         * BORDER_TRANSPARENT
         * uvwxyz|absdefgh|ijklmno
         * BORDER_REFLECT101
         * same as BORDER_REFLECT_101
         * BORDER_DEFAULT
         * same as BORDER_REFLECT_101
         * BORDER_ISOLATED
         * do not look outside of ROI
         *
         *   第6个参数：const Scalar& borderValue=Scalar()：边界的颜色设置，一般默认是0
         */
        Imgproc.warpAffine(splitImage, imgDst, affineTrans, imgDst.size(),
                Imgproc.INTER_CUBIC | Imgproc.WARP_FILL_OUTLIERS,Core.BORDER_CONSTANT,new Scalar(255,255,255));
        return imgDst;
    }


    /**
     * 斜点 移动边直点
     * @param eyesRects
     * @return
     */
    public static int getRotationAngle(Rect[] eyesRects) {
        Rect first = eyesRects[0];
        Rect second = eyesRects[1];

        //取中值
        int first_mid_y = first.y+first.height/2;
        int second_mid_y = second.y+second.height/2;
        int first_mid_x = first.x+first.width/2;
        int second_mid_x = second.x+second.width/2;

        int width = Math.abs(first_mid_x-second_mid_x);
        int height = Math.abs(first_mid_y-second_mid_y);
        int length = (int) Math.sqrt(width*width+height*height);
        int angle = (int)(Math.acos((width*width +length*length - height*height)/(2.0*width*length))*100);


        if(first_mid_y > second_mid_y){
            angle = -angle;
        }
        return angle;
    }
    /**
     * 2个正方形 横向最远点角度
     * @param eyesRects
     * @return
     */
    public static Double getRotationAngleFarthest(Rect[] eyesRects) {
        Rect first =null;
        Rect second = null;
        if(eyesRects[0].x<eyesRects[1].x){
            first = eyesRects[0];
            second = eyesRects[1];
        }else {
            first = eyesRects[1];
            second = eyesRects[0];

        }

        int first_mid_y = first.y;
        int second_mid_y = second.y;
        int first_mid_x = first.x;
        int second_mid_x = second.x+second.width;


        Double center_x = Double.valueOf(first_mid_x+second_mid_x)/2;
        Double center_y = Double.valueOf(first_mid_y+second_mid_y)/2;

        Double width = Math.abs(first_mid_x-center_x);
        Double height = Math.abs(first_mid_y-center_y);
        Double length =  Math.sqrt(width*width+height*height);
        Double angle = (Math.acos((width*width +length*length - height*height)/(2.0*width*length))*100);


        if(first_mid_y < second_mid_y){
            angle = angle;
        }else {
            angle = -(angle);
        }
        return angle;
    }

    public static Integer getRotationAngleByEyes(Mat face) {
        int rotationAngle = 0;
        Double angle = -180.0;
        Mat faceNew = face.clone();
        while ( Math.abs(angle) >1) {
            Rect[] eyesRects = CascadeClassifierUtils.findEyes(faceNew);
            if (eyesRects == null || eyesRects.length != 2) {
                if(rotationAngle != 0){
                    return rotationAngle;
                }
               return 0;
            }
            drawRects(eyesRects,faceNew);
            angle = OpencvImageUtils.getRotationAngleFarthest(eyesRects);
            rotationAngle+=angle;
            faceNew = OpencvImageUtils.rotate3(faceNew,angle);
            Imgcodecs.imwrite("C:/Users/admin/Desktop/cut/eyeglasses_zheng00.jpg", faceNew);
        }
        return rotationAngle;
    }

    public static Mat getIdCard(Mat mat,Rect mix) {
        Imgproc.rectangle(mat,new Rect(mix.x-mix.width*3,mix.y-mix.height,mix.width*9/2,mix.height*3),new Scalar(125, 125, 125),1,8);
        Imgcodecs.imwrite("C:/Users/admin/Desktop/cut/test_tou_xian_hhh.jpg", mat);
        Mat idcard =  new Mat(mat,new Rect(mix.x-mix.width*3,mix.y-mix.height,mix.width*9/2,mix.height*3));
        Imgcodecs.imwrite("C:/Users/admin/Desktop/cut/IdcardCuted.jpg", idcard);
        return idcard;
    }

    public static List<MatOfPoint> findMatOfPoint(Mat src) {
        //灰度化+二值化
        Mat gray = new Mat();
        Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY);
        Imgproc.blur(gray, gray, new Size(1, 1));
        Imgcodecs.imwrite("C:/Users/admin/Desktop/cut/zfx_gray.jpg", gray);
        Mat threshold = new Mat();
        Imgproc.threshold(gray, threshold, 140, 200, Imgproc.THRESH_BINARY);
        Imgcodecs.imwrite("C:/Users/admin/Desktop/cut/zfx_threshold.jpg", threshold);
        System.out.println("二值化通道数=" + threshold.channels());

        //轮廓查找
        List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
        Mat hierarchy = new Mat();
        /*
         * 查找二进制图像中的轮廓。
         * 该函数使用CITE算法：Suzuki85从二进制图像检索轮廓。轮廓是用于形状分析以及对象检测和识别的有用工具。
         * @param image Source，一个8位单通道图像。非零像素被视为1。零像素保持为0，因此图像被视为binary。您可以使用＃compare，＃inRange，
         * ＃threshold，＃adaptiveThreshold，＃Canny等创建灰度或彩色以外的二进制图像。如果模式等于#RETR_CCOMP或#
         * RETR_FLOODFILL，则输入也可以是32位的整数图像（CV_32SC1）。
         * @param轮廓检测到的轮廓。每个轮廓都存储为点的向量。
         * @param层次结构可选的输出向量 包含有关图像拓扑的信息。它具有与轮廓数量一样多的元素。对于每个第i个轮廓轮廓[i]，元素等级[i] [0]，等级[i] [1]，等级[i]
         * [2]和等级[i][3]均设置为0-在相同的层次级别上，基于下一个和上一个轮廓的轮廓的索引，分别是第一个子轮廓和父轮廓。如果对于轮廓i，没有下一个，上一个，父级或嵌套的轮廓
         * ，则hierarchy [i]的相应元素将为负。
         *
         * @param模式轮廓检索模式，请参见#RetrievalModes
         * RETR_EXTERNAL = 0, 只检测最外围轮廓，包含在外围轮廓内的内围轮廓被忽略；
         * RETR_LIST = 1,检测所有的轮廓，包括内围、外围轮廓，但是检测到的轮廓不建立等级关系，彼此之间独立，没有等级关系，这就意味着这个检索模式下不存在父轮廓或内嵌轮廓
         * RETR_CCOMP = 2,检测所有的轮廓，但所有轮廓只建立两个等级关系，外围为顶层，若外围内的内围轮廓还包含了其他的轮廓信息，则内围内的所有轮廓均归属于顶层；
         * RETR_TREE = 3,检测所有轮廓，所有轮廓建立一个等级树结构。外层轮廓包含内层轮廓，内层轮廓还可以继续包含内嵌轮廓
         * RETR_FLOODFILL = 4; 官方没定义 使用此参数需要把输入源转为CV_32SC1
         * Mat dst = new Mat();
         *
         * edges.convertTo(dst,CvType.CV_32SC1);
         * @param方法轮廓近似方法，请参阅#ContourApproximationModes轮廓是从图像ROI中提取的，然后应在整个图像上下文中进行分析。
         * CHAIN_APPROX_NONE = 1,保存物体边界上所有连续的轮廓点到contours向量内；
         * CHAIN_APPROX_SIMPLE = 2,仅保存轮廓的拐点信息，把所有轮廓拐点处的点保存入contours向量内，拐点与拐点之间直线段上的信息点不予保留；
         * CHAIN_APPROX_TC89_L1 = 3,使用teh-Chinl chain 近似算法;
         * CHAIN_APPROX_TC89_KCOS = 4; 使用teh-Chinl chain 近似算法。
         */
        Imgproc.findContours(threshold, contours, hierarchy, Imgproc.RETR_TREE, Imgproc.CHAIN_APPROX_NONE);
        System.out.println("轮廓数量：" + contours.size());
        System.out.println("轮廓类型：" + hierarchy);

        Imgproc.drawContours(src, contours, -1, new Scalar(0, 0, 255), 1);
        Imgcodecs.imwrite("C:/Users/admin/Desktop/cut/zfx_xxx.jpg", src);

       return contours;
    }


}
